/*
	Copyright (C) 2011 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as
	published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with illusion. 
	If not, see <http://www.gnu.org/licenses/>.
*/

#include <klib/lib.h>
#include <kernel/console.h>
#include <kernel/input.h>
#include <kernel/errors.h>
#include <kernel/list.h>
#include <kernel/kmalloc.h>
#include <kernel/sched.h>

struct key_q {
	uint_t key;
	uint_t state;
	struct key_q *next, *prev;
};

#define MAX_KEY_Q 100

static struct process_waitlist *key_q_waits, *key_q_waits_tail;
static struct key_q *key_qs, *key_qs_tail;
static uint_t key_q_count = 0;

static void queue_key(uint_t key, uint_t state)
{
	struct key_q *key_q = (struct key_q *)kmalloc(sizeof(struct key_q), "dev");
	key_q->key = key;
	key_q->state = state;
	key_q->next = key_q->prev = null;
	list_attach(key_qs, key_qs_tail, key_q);
	key_q_count++;
}

static void unqueue_key(uint_t *key, uint_t *state)
{
	//Pop off a key from the head
	struct key_q *tail_key = key_qs;
	list_detach(key_qs, key_qs_tail, tail_key);
	*key = tail_key->key;
	*state = tail_key->state;
	kfree(tail_key, sizeof(struct key_q), "dev");
	key_q_count--;
}

static int received_key(struct input_driver *driver, uint_t key, uint_t state)
{
	if(key_q_count < MAX_KEY_Q) {
		queue_key(key, state);
		if(key_q_waits) {
			//Wake people waiting on this key
			sched_wake_waitlist(&key_q_waits, &key_q_waits_tail);
			//Release to the newly available processes
			sched_release();
		}
		return 0;
	} else {
		debug("Input key queue full. Last source: %s\n", driver->name);
		return ERESRC;
	}
}

int input_read_key(uint_t *key, uint_t *state)
{
	if(key_q_count == 0) {
		//Make the process wait on the key_q_waits
		sched_wait_process(&key_q_waits, &key_q_waits_tail,
				current_process);
		sched_release();
		//We have shit available. Read it.
		if(key_q_count == 0) {
			return ENODATA;
		}
	}
	unqueue_key(key, state);
	return 0;
}

static struct input_driver *drivers, *drivers_tail;

void input_register_driver(struct input_driver *driver)
{
	initmsg("Input driver: %s\n", driver->name);
	list_attach(drivers, drivers_tail, driver);
	driver->received_key = received_key;
}

void input_unregister_driver(struct input_driver *driver)
{
	list_detach(drivers, drivers_tail, driver);
}
